package id.co.rho.scoring.cid.server.command.handler.impl;

import id.co.rho.scoring.cid.client.command.impl.ScoringModelCommand;
import id.co.rho.scoring.cid.client.command.impl.ScoringModelCommand.CommandType;
import id.co.rho.scoring.cid.client.command.impl.ScoringModelResult;
import id.co.rho.scoring.cid.client.model.CriteriaTree;
import id.co.rho.scoring.cid.client.utils.Folder;
import id.co.rho.scoring.cid.domain.Criteria;
import id.co.rho.scoring.cid.domain.CriteriaType;
import id.co.rho.scoring.cid.domain.Model;
import id.co.rho.scoring.cid.server.command.handler.CommandHandler;
import id.co.rho.scoring.cid.server.service.ScoringModelService;
import id.co.rho.scoring.cid.server.util.AppContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;

public class ScoringModelHandler implements
		CommandHandler<ScoringModelCommand, ScoringModelResult> {
	List<CriteriaTree> critTrees;
	Folder folders;
	Map<Long, CriteriaTree> listOfTree;
	Map<Integer, CriteriaTree> treeSequence = new HashMap<Integer, CriteriaTree>();
	ScoringModelService svc;
	Integer position = 0;
	ScoringModelCommand command;
	Integer compareStep = 0;

	@Override
	public ScoringModelResult execute(ScoringModelCommand command) {
		this.command = command;
		critTrees = new ArrayList<CriteriaTree>();
		ScoringModelResult res = new ScoringModelResult();
		ApplicationContext ctx = AppContext.getApplicationContext();
		svc = ctx.getBean(ScoringModelService.class);
		if (command.getCommandType() == CommandType.EDIT) {
			Criteria model = svc.getCriteriaById(command.getId());
			model.setCriteriaName(command.getCriteriaName());
			model.setCriteriaNumber(command.getCriteriaNumber());
			model.setCriteriaRank(command.getCriteriaRank());
			model.setCriteriaType(CriteriaType.valueOf(command
					.getCriteriaType()));
			model.setCriteriaWeight(command.getCriteriaWeight());
			model.setParent(svc.getCriteriaById(command.getParent()));
			Criteria mod = svc.saveOrUpdate(model);
			if (mod != null) {
				res.setSaved(true);
			} else {
				res.setSaved(false);
			}
		}else if(command.getCommandType() == CommandType.DELETE){
			Model m = svc.getModelById(command.getModelId());
			svc.deleteModel(m);
			res.setSaved(true);
		} else if (command.getCommandType() == CommandType.ADD) {
			Criteria model = new Criteria();
			model.setCriteriaName(command.getCriteriaName());
			model.setCriteriaNumber(command.getCriteriaNumber());
			model.setCriteriaRank(command.getCriteriaRank());
			model.setCriteriaType(CriteriaType.valueOf(command
					.getCriteriaType()));
			model.setCriteriaWeight(command.getCriteriaWeight());
			if (command.getParent() == 0) {
			} else {
				model.setParent(svc.getCriteriaById(command.getParent()));
			}

			Criteria mod = svc.saveOrUpdate(model);
			if (mod != null) {
				if (command.getParent() == 0) {
					Model mode = svc.getModelById(command.getModelId());
					mode.getCriteries().add(mod);
					Model mode2;
					mode2 = svc.saveOrUpdateModel(mode);
					if (mode2 != null) {
						res.setSaved(true);
					} else {
						res.setSaved(false);
					}
				} else {
					res.setSaved(true);
				}

			} else {
				res.setSaved(false);
			}

		} else if (command.getCommandType() == CommandType.COMPARE) {

			compareStep = 1;
			List<Criteria> crits = svc.getCriteriaByModelId(command
					.getModelId());
			Folder root = new Folder("root");
			Collections.sort(crits, COMPARATOR);
			listOfTree = new HashMap<Long, CriteriaTree>();
			treeSequence = new HashMap<Integer, CriteriaTree>();
			position = 0;
			for (int i = 0; i < crits.size(); i++) {
				this.addTree(crits.get(i));
				this.searchChild(crits.get(i).getId());
			}
			root = new Folder("root");
			for (Map.Entry<Long, CriteriaTree> entry : listOfTree.entrySet()) {
				List<Criteria> criterias = svc.getCriteriaByParentId(entry
						.getValue().getId());
				Collections.sort(criterias, COMPARATOR);
				if (criterias.size() > 0) {
					for (int i = 0; i < criterias.size(); i++) {
						entry.getValue().add(
								listOfTree.get(criterias.get(i).getId()));
					}
				}
			}

			if (crits.size() > 0) {
				for (int i = 0; i < crits.size(); i++) {
					root.add(listOfTree.get(crits.get(i).getId()));
				}
			}

			res.folder = root;
			compareStep = 2;
			crits = svc.getCriteriaByModelId(command.getModelId2());
			Collections.sort(crits, COMPARATOR);
			listOfTree = new HashMap<Long, CriteriaTree>();
			position = 0;
			for (int i = 0; i < crits.size(); i++) {
				this.addTree(crits.get(i));
				this.searchChild(crits.get(i).getId());
			}
			root = new Folder("root");
			for (Map.Entry<Long, CriteriaTree> entry : listOfTree.entrySet()) {
				List<Criteria> criterias = svc.getCriteriaByParentId(entry
						.getValue().getId());
				Collections.sort(criterias, COMPARATOR);
				if (criterias.size() > 0) {
					for (int i = 0; i < criterias.size(); i++) {
						entry.getValue().add(
								listOfTree.get(criterias.get(i).getId()));
					}
				}
			}

			if (crits.size() > 0) {
				for (int i = 0; i < crits.size(); i++) {
					root.add(listOfTree.get(crits.get(i).getId()));
				}
			}

			res.folder2 = root;

		} else if (command.getCommandType() == CommandType.SAVE_ALL) {
			List<CriteriaTree> treeList = command.getTreeList();
			Model mod = svc.getModelById(command.getModelId());
			Model newModel = new Model();
			newModel.setModelName(mod.getModelName());
			newModel.setRevision(svc.getModelRev(mod.getModelName()));
			newModel.setTypeModelConfig(mod.getTypeModelConfig());
			newModel.setCreatedDate(new Date());

			for (int i = 0; i < treeList.size(); i++) {
				Criteria crit = new Criteria();
				crit.setCriteriaName((treeList.get(i).get("criteriaName") != null) ? treeList
						.get(i).get("criteriaName").toString()
						: null);
				crit.setCriteriaNumber((treeList.get(i).get("criteriaNumber") != null) ? treeList
						.get(i).get("criteriaNumber").toString()
						: null);
				crit.setCriteriaRank((treeList.get(i).get("criteriaRank") != null) ? Double
						.valueOf(treeList.get(i).get("criteriaRank").toString())
						: null);
				crit.setCriteriaType(CriteriaType.valueOf((treeList.get(i).get(
						"criteriaType") != null) ? treeList.get(i)
						.get("criteriaType").toString() : null));
				crit.setCriteriaWeight((treeList.get(i).get("criteriaWeight") != null) ? Double
						.valueOf(treeList.get(i).get("criteriaWeight")
								.toString()) : null);
				crit.setLocation((treeList.get(i).get("position") != null) ? Integer
						.valueOf(treeList.get(i).get("position").toString())
						: null);

				List<Criteria> childs = getChild(treeList.get(i));
				for (int a = 0; a < childs.size(); a++) {
					childs.get(a).setParent(crit);
					crit.getChild().add(childs.get(a));
				}
				newModel.getCriteries().add(crit);
			}

			Model modl = svc.saveOrUpdateModel(newModel);
			if (modl != null) {
				res.setModelId(modl.getId());
//				Model model = svc.getModelById(command.getId());
				String count = svc.setAsDefault(modl.getId(), modl.getModelName());
				if (Integer.parseInt(count) > 0) {
					res.setSaved(true);
				} else {
				}

			}

		} else {
			List<Criteria> crits = svc.getCriteriaByModelId(command
					.getModelId());
			Collections.sort(crits, COMPARATOR);
			Folder root = new Folder("root");
			listOfTree = new HashMap<Long, CriteriaTree>();
			for (int i = 0; i < crits.size(); i++) {
				this.addTree(crits.get(i));
				this.searchChild(crits.get(i).getId());
			}
			for (Map.Entry<Long, CriteriaTree> entry : listOfTree.entrySet()) {
				List<Criteria> criterias = svc.getCriteriaByParentId(entry
						.getValue().getId());
				Collections.sort(criterias, COMPARATOR);
				if (criterias.size() > 0) {
					for (int i = 0; i < criterias.size(); i++) {
						entry.getValue().add(
								listOfTree.get(criterias.get(i).getId()));
					}
				}
			}

			if (crits.size() > 0) {
				for (int i = 0; i < crits.size(); i++) {
					root.add(listOfTree.get(crits.get(i).getId()));
				}
			}
			res.folder = root;
		}
		return res;
	}

	private List<Criteria> getChild(CriteriaTree parent) {
		List<Criteria> res = new ArrayList<Criteria>();
		List<CriteriaTree> md = parent.getChild();
		if (md.size() > 0) {

			for (int i = 0; i < md.size(); i++) {
				Criteria crit = new Criteria();
				crit.setCriteriaName((md.get(i).get("criteriaName") != null) ? md
						.get(i).get("criteriaName").toString()
						: null);
				crit.setCriteriaNumber((md.get(i).get("criteriaNumber") != null) ? md
						.get(i).get("criteriaNumber").toString()
						: null);
				crit.setCriteriaRank((md.get(i).get("criteriaRank") != null) ? Double
						.valueOf(md.get(i).get("criteriaRank").toString())
						: null);
				crit.setCriteriaType(CriteriaType.valueOf((md.get(i).get(
						"criteriaType") != null) ? md.get(i)
						.get("criteriaType").toString() : null));
				crit.setCriteriaWeight((md.get(i).get("criteriaWeight") != null) ? Double
						.valueOf(md.get(i).get("criteriaWeight").toString())
						: null);
				crit.setLocation((md.get(i).get("position") != null) ? Integer
						.valueOf(md.get(i).get("position").toString()) : null);
				List<Criteria> childs = getChild(md.get(i));
				for (int a = 0; a < childs.size(); a++) {
					childs.get(a).setParent(crit);
					crit.getChild().add(childs.get(a));
				}
				res.add(crit);
			}
		}
		return res;
	}

	private void addTree(Criteria crit) {
		CriteriaTree critTree = new CriteriaTree();
		critTree.setId(crit.getId());
		critTree.setCriteriaName(crit.getCriteriaName());
		critTree.setCriteriaNumber(crit.getCriteriaNumber());
		critTree.setCriteriaRank(crit.getCriteriaRank());
		critTree.setCriteriaWeight(crit.getCriteriaWeight());
		critTree.setPosition(crit.getLocation());
		if (crit.getParent() != null) {
			critTree.setParentId(crit.getParent().getId());
		}
		if (crit.getCriteriaType() != null) {
			critTree.setCriteriaType(crit.getCriteriaType().toString());
		} else {
			critTree.setCriteriaType("CRITERIA");
		}

		if (command.getCommandType() == CommandType.COMPARE) {
			if (treeSequence.containsKey(position)) {
				if (!this.isEqualCriteria(treeSequence.get(position), critTree)) {
					critTree.setDiff("true");
				}
			} else if (!treeSequence.containsKey(position) && compareStep == 2) {
				critTree.setDiff("new");
			} else {
				treeSequence.put(position, critTree);
			}
			position++;
		}
		listOfTree.put(crit.getId(), critTree);

	}

	private void searchChild(Long id) {
		List<Criteria> crits = svc.getCriteriaByParentId(id);
		Collections.sort(crits, COMPARATOR);
		if (crits.size() > 0) {
			for (int i = 0; i < crits.size(); i++) {
				this.addTree(crits.get(i));
				this.searchChild(crits.get(i).getId());
			}
		}
	}

	public Boolean isEqualCriteria(CriteriaTree left, CriteriaTree right) {
		Boolean result = true;

		if (!left.getCriteriaName().equals(right.getCriteriaName())) {
			result = false;
			right.setNameDiff("true");
		}

		if (left.getCriteriaNumber() != null
				&& right.getCriteriaNumber() != null) {
			if (!left.getCriteriaNumber().equals(right.getCriteriaNumber())) {
				result = false;
				right.setNumberDiff("true");
			}
		}

		if (left.getCriteriaNumber() == null
				&& right.getCriteriaNumber() != null) {
			result = false;
			right.setNumberDiff("true");
		}

		if (left.getCriteriaNumber() != null
				&& right.getCriteriaNumber() == null) {
			result = false;
			right.setNumberDiff("true");
		}
		if (left.getCriteriaType() != null && right.getCriteriaType() != null) {
			if (!left.getCriteriaType().equals(right.getCriteriaType()))
				result = false;
		}
		if (left.getCriteriaType() == null && right.getCriteriaType() != null) {
			result = false;
		}
		if (left.getCriteriaType() != null && right.getCriteriaType() == null) {
			result = false;
		}
		if (left.getCriteriaWeight() != null
				&& right.getCriteriaWeight() != null) {
			if (left.getCriteriaWeight().compareTo(right.getCriteriaWeight()) != 0)
				result = false;
		}
		if (left.getCriteriaWeight() == null
				&& right.getCriteriaWeight() != null) {
			result = false;
		}
		if (left.getCriteriaWeight() != null
				&& right.getCriteriaWeight() == null) {
			result = false;
		}
		if (left.getCriteriaRank() != null && right.getCriteriaRank() != null) {
			if (left.getCriteriaRank().compareTo(right.getCriteriaRank()) != 0)
				result = false;
		}
		if (left.getCriteriaRank() == null && right.getCriteriaRank() != null) {
			result = false;
		}
		if (left.getCriteriaRank() != null && right.getCriteriaRank() == null) {
			result = false;
		}

		return result;
	}

	@Override
	public Class<ScoringModelCommand> getActionType() {
		return ScoringModelCommand.class;
	}

	private static Comparator<Criteria> COMPARATOR = new Comparator<Criteria>() {
		public int compare(Criteria o1, Criteria o2) {
			return o1.getLocation() - o2.getLocation();
		}
	};

}
